#include <queue>
#include <stack>
#include <thread>
#include <atomic>
#include "ZTouchProto.h"
#include "ui/SessionsActive.hpp"
#include "ui/DialogUI.hpp"

#define SERVERADDR "47.94.91.248"
#define LOCALADDR "127.0.0.1"
#define PORT 32777
#define KEEPTIME 20 
#define RETRYTIME 3

//define LOCALTEST

static inline void block_wait_for_sec(long sec) {usleep(sec * 1000000);}

using namespace std;
using namespace ZTouch;

atomic_flag enabledFlag = ATOMIC_FLAG_INIT;

//心跳机制
static void keep(socket_t connect, mutex* enabled)
{
	int roundLeft = 0;
	while( !(enabled->try_lock()) )
	{
		if((--roundLeft) <= 0)
		{
			ZMessage::sendKeep(connect, MSG_WAITALL);
			roundLeft = KEEPTIME;
		}
		block_wait_for_sec(1);
	}
	enabled->try_lock();
	enabled->unlock();
}

static inline socket_t initNetwork(const char *serverAddress = SERVERADDR, int port = 32777)
{
	struct sockaddr_in serverAddr;
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.s_addr = inet_addr(serverAddress);
	serverAddr.sin_port = htons(port);
	memset(serverAddr.sin_zero, 0, 8);
	
	socket_t sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	
	while(connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) != 0)
	{
		cout<<endl<<"Trying to Connect SERVER at "<<serverAddress<<flush;
		block_wait_for_sec(RETRYTIME);
	}
	
	return sock;
}

static inline string rmCR(string str)
{
	size_t lastp = str.length()-1;
	if(str[lastp] == '\n') str[lastp] = '\0';
	
	for(size_t i = 0; i <= lastp; ++i)
	{
		if(str[i] == '\n') return str.substr(0, i) + "...";
	}
	
	return str;
}

static void logout(socket_t connect, mutex* enabled)
{
	ZTouchDataHead head(0, ZTouchDataHead::LOGOUT);
	ZMessage::putHead(connect, head, MSG_DONTWAIT);
	enabledFlag.clear();
	Ui::tipsDialog("You have been logged out.");
	enabled->try_lock();
	enabled->unlock();
}

static void listenMessage
(
	socket_t connect, mutex* enabled, 
	Ui::ActiveBase* sessionsMenu, Ui::ActiveBase** currentMenu
)
{
	ZTouchDataHead head;
	while( !(enabled->try_lock()) )
	{
		if(ZMessage::getHead(connect, head, MSG_WAITALL))
		{
			if(head.getType() == ZTouchDataHead::CHATING)
			{
				ZMessage recvMessage;
				if(recvMessage.load(connect, head, MSG_WAITALL))
				{
					const char *const sender = recvMessage.getSrcName();
					const char *const conten = recvMessage.getContent();
					sessionsMenu->getMessage(sender, conten);
					
					if(*currentMenu != sessionsMenu  &&  (*currentMenu)->getActiveId() == sender)
					{
						(*currentMenu)->getMessage(sender, conten);
					}
				}
				else break;
			}
			else
			{
				if(*currentMenu != sessionsMenu) switch(head.getType())
				{
					/*
					case ZTouchDataHead::SENDSUCCEED:
						cout<<endl<<"<SEND SUCCEED>"<<flush;
					break;*/
					case ZTouchDataHead::SENDFAIL:
						(*currentMenu)->getSystemMessage("Message send fail: UNKNOWN ERROR");
					break;
					case ZTouchDataHead::DESNOTFOUND:
						(*currentMenu)->getSystemMessage("Message send fail: He(She) might be offline");
					break;
					
					//服务器说：连接可以关闭了
					case ZTouchDataHead::TERMI:
						close(connect);
						goto breakLine;
					break;
					
					//服务器说：客户端请登出
					case ZTouchDataHead::LOGOUT:
						logout(connect, enabled);
					break;
					default: break;
				}
			}
		}
		else break;
	}
	breakLine:
	enabled->try_lock();
	enabled->unlock();
}

static void clientSession(socket_t connect, mutex* enabled, const string& myName)
{
	//ZTouchDataHead recvHead;
	
	Ui::ActiveBase *const sessionsMenu = new Ui::SessionsActive("#session0", connect, myName);
	Ui::ActiveBase *currentMenu = sessionsMenu;

	thread listenMessageThread(listenMessage, connect, enabled, sessionsMenu, &currentMenu);
	
	currentMenu->printNew();
	
	char opt = 0;
	int temp = 0;
	for(Ui::ActiveBase* selectedMenu = nullptr; enabledFlag.test_and_set();)
	{
		if(currentMenu == nullptr) continue;
		opt = getch();
		if((unsigned) opt <= 127) switch(opt)
		{
			case C_CONTROLKEY_ESC: case CTRLF:LEFT:
				if(currentMenu->menuControl_left())
				{
					if(currentMenu == sessionsMenu)
					{
						if((temp = Ui::confirmDialog("Do you want to exit?")) == 1)
						{
							delete sessionsMenu;
							logout(connect, enabled);
							goto breakPoint;
						}
						else if(temp == 2)
						{
							switch(getch())
							{
								case 65: currentMenu->menuControl_up();		break;
								case 66: currentMenu->menuControl_down();	break;
								case 67:
									if((selectedMenu = currentMenu->menuControl_right()) != nullptr)
									{
										currentMenu = selectedMenu;
										currentMenu->printNew();
									}
								break;
								case 68: goto LEFT;		break;
							}
						}
						currentMenu->printNew();
					}
					else if(opt != C_CONTROLKEY_ESC)
					{
						delete currentMenu;
						currentMenu = sessionsMenu;
						currentMenu->printNew(true);
					}
					else
					{
						if(getch() != 27) switch(getch())
						{
							case 65: currentMenu->menuControl_up();		break;
							case 66: currentMenu->menuControl_down();	break;
							case 67: currentMenu->menuControl_getInto();break;
							case 68:  
								delete currentMenu;
								currentMenu = sessionsMenu;
								currentMenu->printNew(true);
							break;
						}
						else
						{
							delete currentMenu;
							currentMenu = sessionsMenu;
							currentMenu->printNew(true);
						}
					}
				}
			break;
			case CTRRG:
				if((selectedMenu = currentMenu->menuControl_right()) != nullptr)
				{
					currentMenu = selectedMenu;
					currentMenu->printNew();
				}
			break;
			case CTRUP:	currentMenu->menuControl_up();			break;
			case CTRDW: currentMenu->menuControl_down();		break;
			case CTRUL: currentMenu->menuControl_up_left();		break;
			case CTRUR: currentMenu->menuControl_up_right();	break;
			case CTRDL: currentMenu->menuControl_down_left();	break;
			case CTRDR: currentMenu->menuControl_down_right();	break;
			case CTRCF: case '\n': case '\r':
				if((selectedMenu = currentMenu->menuControl_getInto()) != nullptr)
				{
					currentMenu = selectedMenu;
					currentMenu->printNew();
				}
			break;
			default: currentMenu->menuControl_opts(&opt, 1);
		}
		else
		{
			static char wcharBuffer[5] = {0,0,0,0,0};
			int length = 0;
			wcharBuffer[0] = opt;
			//110xxxxx:192
			//1110xxxx:224
			//11110xxx:240
			//11111000:248
			if((opt & 248) == 240)
			{
				wcharBuffer[1] = getch();
				wcharBuffer[2] = getch();
				wcharBuffer[3] = getch();
				length = 4;
			}
			else if((opt & 240) == 224)
			{
				wcharBuffer[1] = getch();
				wcharBuffer[2] = getch();
				wcharBuffer[3] = '\0';
				length = 3;
			}
			else if((opt & 224) == 192)
			{
				wcharBuffer[1] = getch();
				wcharBuffer[2] = '\0';
				wcharBuffer[3] = '\0';
				length = 2;
			}
			
			currentMenu->menuControl_opts(wcharBuffer, length);
		}
	}
	breakPoint:
	
	enabled->try_lock();
	enabled->unlock();
	
	listenMessageThread.join();
}

int main(int argc, char *argv[])
{
	enabledFlag.test_and_set();
	int port = 32777;
	socket_t connect = -1;
	#ifdef __ANDROID__
	#	ifdef LOCALTEST
		cout<<endl<<"<USING PORT> -> ";
		cin>>port;
		cin.get();
	#	endif
	#endif
	string myName;
	login: myName = "";
	while(myName == "")
	{
		myName = Ui::nameInputDialog("Username:");
	}
	#ifdef LOCALTEST
		connect = initNetwork(LOCALADDR, port);
	#else
		connect = initNetwork(SERVERADDR, port);
	#endif
	if(connect == -1)
	{
		//TODO: connect fail
	}
	
	switch(ZMessage::requestLogin(connect, myName))
	{
		case ZTouchDataHead::LOGINSUCCEED:
			//cout<<endl<<"<LOGIN SUCCEED> -> Press Enter Key to Start!"<<flush;
		break;
		case ZTouchDataHead::LOGINFAIL:
			cout<<endl<<endl<<endl<<"\t<LOGIN FAIL> -> The username you used might been exist."<<flush;
			cout<<endl<<"\t<ENTER KEY TO RETRY>"<<flush;
			cin.get();
			goto login;
		break;
		default:
			cout<<endl<<endl<<endl<<"\t<LOGIN ERROR>"<<flush;
		break;
	}
	
	mutex enabled;
	enabled.lock();
	thread keepThread(keep, connect, &enabled);
	clientSession(connect, &enabled, myName);
	
	keepThread.join();
	close(connect);
	return 0;
}
